类型别名 vs 接口

在 TypeScript 中,类型别名(Type Aliases)和接口(Interfaces)是两种常用的定义数据结构的方式。虽然它们在很多情况下可以互换使用,但它们之间存在一些关键的区别。理解这些区别对于编写高效且易于维护的 TypeScript 代码至关重要。

1. 类型别名(Type Aliases)

类型别名允许你为现有的类型创建一个新的名字。它可以用于基本类型、联合类型、交叉类型、元组等。类型别名不会创建新的类型,它只是为现有类型提供一个别名。

1.1 基本用法

type Name = string;
type Age = number;
type Person = {
    name: Name;
    age: Age;
};

const person: Person = {
    name: "Alice",
    age: 30
};

在这个例子中,Name 和 Age 是基本类型的别名,而 Person 是一个对象类型的别名。

1.2 联合类型和交叉类型

类型别名也可以用于联合类型和交叉类型:

type ID = number | string;
type Employee = Person & { employeeId: ID };

const employee: Employee = {
    name: "Bob",
    age: 25,
    employeeId: "E12345"
};

在这个例子中,ID 是一个联合类型,而 Employee 是一个交叉类型。

2. 接口(Interfaces)

接口是 TypeScript 中定义对象结构的主要方式。它用于描述对象的形状,并且可以被类实现。接口可以扩展其他接口,从而实现继承。

2.1 基本用法

interface Person {
    name: string;
    age: number;
}

const person: Person = {
    name: "Alice",
    age: 30
};

在这个例子中,Person 接口定义了一个对象的形状,包含 name 和 age 两个属性。

2.2 扩展接口

接口可以扩展其他接口,从而实现继承:

interface Employee extends Person {
    employeeId: string;
}

const employee: Employee = {
    name: "Bob",
    age: 25,
    employeeId: "E12345"
};

在这个例子中,Employee 接口扩展了 Person 接口,并添加了一个新的属性 employeeId。

3. 类型别名 vs 接口

3.1 语法差异

  • 类型别名 使用 type 关键字:

    type Name = string;
    
  • 接口 使用 interface 关键字:

    interface Person {
        name: string;
        age: number;
    }
    

3.2 扩展性

  • 类型别名 不能被扩展,但可以通过交叉类型实现类似的效果:

    type Employee = Person & { employeeId: string };
    
  • 接口 可以扩展其他接口:

    interface Employee extends Person {
        employeeId: string;
    }
    

3.3 联合类型和交叉类型

  • 类型别名 非常适合用于联合类型和交叉类型:

    type ID = number | string;
    
  • 接口 不能直接用于联合类型和交叉类型,但可以通过类型别名来实现:

    type ID = number | string;
    interface Employee extends Person {
        employeeId: ID;
    }
    

3.4 类型合并

  • 接口 支持类型合并,即相同名称的接口会自动合并:

    interface Person {
        name: string;
    }
    
    interface Person {
        age: number;
    }
    
    const person: Person = {
        name: "Alice",
        age: 30
    };
    
  • 类型别名 不支持类型合并,重复定义会报错:

    type Person = {
        name: string;
    };
    
    type Person = { // 报错:Duplicate identifier 'Person'.
        age: number;
    };
    

4. 实践练习

4.1 练习1:定义一个类型别名

定义一个类型别名 Coordinate,表示一个二维坐标点,包含 x 和 y 两个属性,类型均为 number。

type Coordinate = {
    x: number;
    y: number;
};

const point: Coordinate = {
    x: 10,
    y: 20
};

4.2 练习2:定义一个接口

定义一个接口 Shape,表示一个几何形状,包含 name 属性(类型为 string)和 area 方法(返回类型为 number)。

interface Shape {
    name: string;
    area(): number;
}

class Circle implements Shape {
    constructor(public radius: number) {}

    name = "Circle";

    area(): number {
        return Math.PI * this.radius ** 2;
    }
}

const circle = new Circle(5);
console.log(circle.area()); // 输出: 78.53981633974483

4.3 练习3:扩展接口

定义一个接口 Rectangle,扩展 Shape 接口,并添加 width 和 height 属性(类型均为 number)。

interface Rectangle extends Shape {
    width: number;
    height: number;
}

class RectangleImpl implements Rectangle {
    constructor(public width: number, public height: number) {}

    name = "Rectangle";

    area(): number {
        return this.width * this.height;
    }
}

const rectangle = new RectangleImpl(10, 20);
console.log(rectangle.area()); // 输出: 200

5. 总结

类型别名和接口是 TypeScript 中定义数据结构的两种主要方式。类型别名更适合用于基本类型、联合类型和交叉类型,而接口更适合用于定义对象的形状,并且支持扩展和类型合并。在实际开发中,根据具体需求选择合适的方式来定义类型,可以使代码更加清晰和易于维护。

通过本教程的学习,你应该能够理解类型别名和接口的区别,并能够在实际项目中灵活运用它们。